import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.DescribeInstanceStatusRequest;
import com.amazonaws.services.ec2.model.DescribeInstanceStatusResult;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.InstanceStatus;
import com.amazonaws.services.ec2.model.Reservation;
import com.amazonaws.services.ec2.model.RunInstancesRequest;
import com.amazonaws.services.ec2.model.RunInstancesResult;
import com.amazonaws.services.ec2.model.TerminateInstancesRequest;

/**
 * 
 * @author William Droz, Raphaël Capocasale
 * 
 * <pre>
 * This class lets you use AWS instances to handle a picture
 * 
 * prerequisite :
 * 1) the instances have to contain WXCH app. The path of XWCH is /home/ubuntu/XtremWeb-CH_Worker/
 * 2) a script (changeFile.sh) has to be localised in each instance. It lets us change the config file of XWCH. The path of the script is /home/ubuntu/changeFile.sh
 * 3) the key file for the intance connection has to be in chmod 600. It has to be localised in a key directory, next to this app
 * 4) name_image.zip has to be localised in the same directory that to this app. This zip file contains a name_image.pov file
 * 5) the config.ini has to be localised in the same directory that this app
 * </pre>
 */
public class AwsInstancesManager implements Runnable {

	private final static String SCRIPT_PATH = "/home/ubuntu/changeFile.sh";
	private final static String START_SCRIPT_PATH = "/home/ubuntu/XtremWeb-CH_Worker/startxw.sh";
	private AmazonEC2 m_ec2;
	private RunInstancesRequest m_runInstancesRequest;
	private RunInstancesResult m_runInstancesResult;
	private List<String> m_listIPs;
	private List<String> m_listIDs;
	private List<Thread> m_listThreads;
	private long m_tokenName;
	private String m_ami;
	private String m_keyname;
	private String m_prefixWorker;
	private String m_prefixCombiner;
	private String m_securityGroup;
	private String m_instanceType;
	private int m_minNbInstance;
	private int m_maxNbInstance;
	private String m_idClient;
	private String m_addressServer;
	private String m_image;
	private int m_height;
	private int m_width;
	private int m_k;
	private String m_povrayModId;
	private String m_combineModId;
	private String m_regExpPovRayHosts;
	private String m_regExpCombineHost;
	private String m_key;
	private String m_user;
	private String m_awsSecretKey;
	private String m_awsAccessKeyId;
	private String m_applicationName;

	/**
	 * The constructor starts the AWS instances, recuperates the instances IDs
	 * and the instances IPs.
	 * 
	 * @param properties
	 */
	public AwsInstancesManager(Properties properties) {
		System.out.println("--- INITIALIZATION ---");
		fillAttributes(properties);

		m_ec2 = new AmazonEC2Client(new AWSCredentials() {

			@Override
			public String getAWSSecretKey() {
				return m_awsSecretKey;
			}

			@Override
			public String getAWSAccessKeyId() {
				return m_awsAccessKeyId;
			}
		});
		m_regExpPovRayHosts = m_prefixWorker + ".*";
		m_regExpCombineHost = m_prefixCombiner + ".*";

		runOnAmazon();
		fillIDs();
		waitRunning();
		fillIPs();

		m_listThreads = new ArrayList<>();

	}

	/**
	 * This function is the entry point. The different tasks in the order are :
	 * 1) the propertie file of XWCH,in each instance, is modified
	 * 2) the bash script to run XWCH, in each instance, is called (in threads)
	 * 3) the XWCH API is used
	 * 4) the threads which have called the script to run XWCH, are killed
	 * 5) the AWS instance are terminated
	 */
	@Override
	public void run() {
		for (final String host : m_listIPs) {
			// A script (changeFile.sh), installed in the instance, changes the
			// token name
			// The script has to be called and needs a parameter which is the
			// token name with a number
			String command = SCRIPT_PATH + " " + getTokenName();
			System.out.println("waiting for " + host);
			waitingServicesReady(m_user, host, m_key, command);
			System.out.println("config.properties modified for " + host);
			System.out.println("run job for " + host);

			// Then the script XWCH is called here. Each call is done in a
			// thread
			doJobBackground(m_user, m_key, host);
		}

		// The below lines are instructions using the existing work to use XWCH
		XWCHHandlerPovray test = new XWCHHandlerPovray();
		test.XWCHRunPovRay(m_idClient, m_addressServer, m_image, m_height, m_width, m_k, m_povrayModId, m_combineModId, m_regExpPovRayHosts,
				m_regExpCombineHost, m_applicationName);

		// When the work is finished, the threads have to be killed manually
		killThreads();

		// The AWS instances have to be terminated, too
		removeInstances();

		System.out.println("the end !");

	}

	/**
	 * This function lets us connect to an instance to call the XWCH run script
	 * The call is done in a thread because the script doesn't end.
	 * 
	 * @param user
	 * @param key
	 * @param host
	 */
	private void doJobBackground(final String user, final String key, final String host) {
		System.out.println("connected to " + host + " ok");
		Thread iThread = new Thread(new Runnable() {

			@Override
			public void run() {
				Runtime r = Runtime.getRuntime();
				try {
					String command = "sh " + START_SCRIPT_PATH;
					Process p = r.exec("ssh -t -t -oStrictHostKeyChecking=no -i " + key + " " + user + "@" + host + " " + command);
					p.waitFor();

				} catch (IOException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		iThread.start();
		m_listThreads.add(iThread);
	}

	@SuppressWarnings("deprecation")
	private void killThreads() {
		System.out.println("Closing of the threads...");
		for (Thread t : m_listThreads) {
			t.stop();
		}
	}

	private void removeInstances() {
		System.out.println("The AWS instances are going to be terminated...");
		TerminateInstancesRequest terminateInstancesRequest = new TerminateInstancesRequest(m_listIDs);
		m_ec2.terminateInstances(terminateInstancesRequest);
	}

	private void waitingServicesReady(String user, String host, String key, String command) {
		boolean isOK;
		do {
			isOK = true;
			Runtime r = Runtime.getRuntime();
			try {
				Process p = r.exec("ssh -oStrictHostKeyChecking=no -i " + key + " " + user + "@" + host + " " + command);
				p.waitFor();
				if (p.exitValue() != 0) {
					isOK = false;
				}

			} catch (IOException e) {
				e.printStackTrace();
				isOK = false;
			} catch (InterruptedException e) {
				e.printStackTrace();
				isOK = false;
			}
		} while (!isOK);
	}

	private void waitRunning() {
		System.out.println("Waiting for instances in running state...");
		List<InstanceStatus> listInstanceStatus = getInstancesStatusList();

		boolean isAllInstanceRun;
		do {
			isAllInstanceRun = true;

			for (InstanceStatus instance : listInstanceStatus) {
				if (!instance.getInstanceState().getName().equalsIgnoreCase("running")) {
					isAllInstanceRun = false;
				}
			}
		} while (!isAllInstanceRun);
	}

	private void fillIDs() {
		List<Instance> listInstances = m_runInstancesResult.getReservation().getInstances();
		m_listIDs = new LinkedList<>();
		for (Instance instance : listInstances) {
			m_listIDs.add(instance.getInstanceId());
		}
	}

	private List<InstanceStatus> getInstancesStatusList() {
		DescribeInstanceStatusRequest describeInstanceRequest = new DescribeInstanceStatusRequest().withInstanceIds(m_listIDs);
		DescribeInstanceStatusResult describeInstanceResult = m_ec2.describeInstanceStatus(describeInstanceRequest);
		List<InstanceStatus> listInstanceStatus = describeInstanceResult.getInstanceStatuses();
		while (listInstanceStatus.size() < 1) {
			// Do nothing, just wait, have thread sleep if needed
			describeInstanceResult = m_ec2.describeInstanceStatus(describeInstanceRequest);
			listInstanceStatus = describeInstanceResult.getInstanceStatuses();
		}
		return listInstanceStatus;
	}

	private void fillIPs() {
		System.out.println("The IPs instances are going to be retrieved...");
		m_listIPs = new ArrayList<>();
		DescribeInstancesResult describeInstancesRequest = m_ec2.describeInstances();
		List<Reservation> reservations = describeInstancesRequest.getReservations();
		for (Reservation reservation : reservations) {
			for (Instance instance : reservation.getInstances()) {
				if (m_listIDs.contains(instance.getInstanceId())) {
					m_listIPs.add(instance.getPublicIpAddress());
				}
			}
		}
	}

	private void runOnAmazon() {
		System.out.println("AWS instances are going to start...");
		m_runInstancesRequest = new RunInstancesRequest();
		m_runInstancesRequest.withImageId(m_ami).withInstanceType(m_instanceType).withMinCount(m_minNbInstance).withMaxCount(m_maxNbInstance)
				.withKeyName(m_keyname).withSecurityGroups(m_securityGroup);

		// run instances
		m_runInstancesResult = m_ec2.runInstances(m_runInstancesRequest);
	}

	private String getTokenName() {
		return m_prefixWorker + ++m_tokenName;
	}

	/**
	 * All the attributes are assigned here with the values of the config.ini
	 * 
	 * @param properties
	 */
	private void fillAttributes(Properties properties) {
		m_awsSecretKey = properties.getProperty("awsSecretKey", "o8WiUGUe4ebqkvbPnFT1g25eqY0NKH/Er5xqYKp5");
		m_awsAccessKeyId = properties.getProperty("awsAccessKeyId", "AKIAIRH2PD4Q5KPVDQHA/Er5xqYKp5");
		m_tokenName = Long.valueOf(properties.getProperty("tokenName", "0"));
		m_prefixWorker = properties.getProperty("prefixWorker", "droz-capo");
		m_prefixCombiner = properties.getProperty("prefixCombiner", "droz-capo");
		m_ami = properties.getProperty("ami", "ami-ab1531c2");
		m_keyname = properties.getProperty("keyname", "task5");
		m_instanceType = properties.getProperty("instanceType", "m1.small");
		m_securityGroup = properties.getProperty("securityGroup", "launch-wizard-2");
		m_maxNbInstance = Integer.valueOf(properties.getProperty("maxNbInstance", "4"));
		m_minNbInstance = Integer.valueOf(properties.getProperty("minNbInstance", "4"));
		m_idClient = properties.getProperty("idClient", "c502f049-541e-4e8f-ae69-e6226fc592b3");
		m_addressServer = properties.getProperty("addressServer", "http://www.lsds-rg.org:8080");
		m_image = properties.getProperty("image", "exposure");
		m_height = Integer.valueOf(properties.getProperty("height", "768"));
		m_width = Integer.valueOf(properties.getProperty("width", "1024"));
		m_k = Integer.valueOf(properties.getProperty("k", "4"));
		m_povrayModId = properties.getProperty("povrayModId", "437333d2-150c-4775-9961-49237602f254");
		m_combineModId = properties.getProperty("combineModId", "959ffe03-77d9-4a02-82ce-b0f6c5c54078");
		m_key = properties.getProperty("key", "key/task5.pem");
		m_user = properties.getProperty("user", "ubuntu");
		m_applicationName = properties.getProperty("applicationName", "povrayCapo-Droz-Amazon-task5");
	}
}
